﻿using UnityEngine;
using System.Collections.Generic;
using System;

namespace GameBasic.BehaviorTree
{
    [Serializable]
    public class BhvNodes
    {
        [SerializeField]
        public List<BhvData> datas;

        [NonSerialized]
        public List<BhvNode> nodes;

        // Runtime
        public Dictionary<string, Func<BhvNode, Bhv>> funcsDic;
        public Func<BhvNode, Bhv> defaultAction;

        #region DATA
        public static void CreateNodes(List<BhvData> datas, ref List<BhvNode> nodes, ref List<BhvNode> tmpArray)
        {
            if (nodes == null)
                nodes = new List<BhvNode>();
            if (tmpArray == null)
                tmpArray = new List<BhvNode>();

            for (int i = 0; i < datas.Count; i++)
            {
                var d = datas[i];

                BhvNode node = new BhvNode();
                // n.id = i;
                node.SetData(d);
                tmpArray.Add(node);

                int parentIndex = d.parent;
                if (parentIndex == -1)
                {
                    nodes.Add(node);
                }
                else
                {
                    BhvNode parent = tmpArray[parentIndex];
                    if (parent.children == null)
                        parent.children = new List<BhvNode>();
                    parent.children.Add(node);
                }
            }
        }

        public static List<BhvData> CreateData(List<BhvNode> nodes)
        {
            List<BhvData> datas = new List<BhvData>();
            CreateData(datas, nodes, -1);
            return datas;
        }

        static void CreateData(List<BhvData> datas, List<BhvNode> nodes, int parent)
        {
            for (int i = 0, length = nodes.Count; i < length; i++)
            {
                BhvNode n = nodes[i];
                BhvData d = n.GetData();
                d.parent = parent;
                datas.Add(d);

                if (n.HasChild())
                {
                    CreateData(datas, n.children, datas.Count - 1);
                }
            }
        }

        /// <summary>
        /// Data to nodes. Resturn a copy of nodes mirror of datas.
        /// </summary>
        /// <returns></returns>
        public List<BhvNode> UpdateNodes()
        {
            // No Data ?
            if (datas == null)
                datas = new List<BhvData>();

            List<BhvNode> stores = new List<BhvNode>();
            if (nodes == null)
                nodes = new List<BhvNode>();
            else
                nodes.Clear();

            for (int i = 0; i < datas.Count; i++)
            {
                var d = datas[i];

                BhvNode node = new BhvNode();
                // n.id = i;
                node.SetData(d);
                stores.Add(node);

                int parentIndex = d.parent;
                if (parentIndex == -1)
                {
                    nodes.Add(node);
                }
                else
                {
                    BhvNode parent = stores[parentIndex];
                    if (parent.children == null)
                        parent.children = new List<BhvNode>();
                    parent.children.Add(node);
                }
            }

            return stores;
        }

        /// <summary>
        /// Nodes to data
        /// </summary>
        public void UpdateData()
        {
            datas.Clear();
            UpdateData(nodes, -1);
        }

        void UpdateData(List<BhvNode> nodes, int parent)
        {
            for (int i = 0, length = nodes.Count; i < length; i++)
            {
                BhvNode n = nodes[i];
                BhvData data = n.GetData();
                data.parent = parent;
                datas.Add(data);

                if (n.HasChild())
                {
                    UpdateData(n.children, datas.Count - 1);
                }
            }
        }
        #endregion

        #region Runtime
        public void Init()
        {
            if (funcsDic == null)
            {
                funcsDic = new Dictionary<string, Func<BhvNode, Bhv>>();

                // create nodes
                List<BhvNode> stores = UpdateNodes();
                // CreateNodes(datas, ref nodes, ref stores);

                // init funcArgs
                foreach (var item in stores)
                    item.funcArgs = CheckFunc(item.name);

                // Add all nodes to resuable
                AddReusable(nodes);
            }
        }

        string[] CheckFunc(string text)
        {
            // not func
            int nameLength = text.Length;
            if (nameLength == 0 || text[nameLength - 1] != ')')
                return null;

            int index = text.IndexOf('(');
            if (index == -1)
                return null;

            // name
            var func = text.Substring(0, index);
            // args
            var args = text.Substring(index + 1, nameLength - index - 2).Split(',');

            // create
            string[] funcArgs = new string[1 + args.Length];
            funcArgs[0] = func;
            Array.Copy(args, 0, funcArgs, 1, args.Length);

            return funcArgs;
        }

        public void AddMethod(Func<BhvNode, Bhv> func)
        {
            string mName = func.Method.Name;
            if (string.IsNullOrEmpty(mName))
                return;

            funcsDic[mName] = func;
        }

        public void AddMethods(params Func<BhvNode, Bhv>[] funcs)
        {
            foreach (var func in funcs)
                AddMethod(func);
        }

        public void AddReusable(BhvNode data)
        {
            string mName = data.name;
            // ignore empty name || root
            if (string.IsNullOrEmpty(mName) || data.type == Bhv.Type.Root)
                return;

            // exist?
            if (!funcsDic.TryGetValue(mName, out Func<BhvNode, Bhv> m))
                funcsDic[mName] = d => Create(data);
        }

        public void AddReusable(List<BhvNode> datas)
        {
            foreach (var d in datas)
                AddReusable(d);
        }

        public Bhv Create()
        {
            if (nodes.Count == 0)
                return null;
            return Create(nodes[0]);
        }

        public Bhv Create(BhvNode node)
        {
            Bhv.Type type = node.type;
            Bhv bhv = null;
            bool isAction = type == Bhv.Type.Action;

            // None || Action
            if (isAction || type == Bhv.Type.None)
            {
                string name = node.name;
                string funcName = node.funcArgs == null ? name : node.funcArgs[0];

                // Func
                if (funcsDic.TryGetValue(funcName, out Func<BhvNode, Bhv> func))
                {
                    bhv = func(node);
                }
                // Action
                else if (defaultAction != null)
                {
                    // Debug.Log("Use default action: " + name);
                    node.type = Bhv.Type.Action;
                    bhv = defaultAction(node);
                }

                if (bhv == null)
                    Debug.Log("No method found for: " + type + "_" + name);
            }
            // Composer
            else
            {
                bhv = Bhv.Create(type);

                for (int i = 0, length = node.ChildCount; i < length; i++)
                {
                    var c = node.children[i];
                    Bhv b = Create(c);
                    if (b != null)
                        bhv.Add(b);
                }
            }

            return bhv;
        }
        #endregion
    }
}